/*
 *  linux/kernel/chr_drv/tty_ioctl.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include <errno.h>
#include <termios.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/tty.h>

#include <asm/io.h>
#include <asm/segment.h>
#include <asm/system.h>

static unsigned short quotient[] = {
    0, 2304, 1536, 1047, 857,
    768, 576, 384, 192, 96,
    64, 48, 24, 12, 6, 3};

static void change_speed(struct tty_struct *tty) {
    unsigned short port, quot;

    if (!(port = tty->read_q.data))
        return;
    quot = quotient[tty->termios.c_cflag & CBAUD];
    cli();
    outb_p(0x80, port + 3);      /* set DLAB */
    outb_p(quot & 0xff, port);   /* LS of divisor */
    outb_p(quot >> 8, port + 1); /* MS of divisor */
    outb(0x03, port + 3);        /* reset DLAB */
    sti();
}

static void flush(struct tty_queue *queue) {
    cli();
    queue->head = queue->tail;
    sti();
}

static void wait_until_sent(struct tty_struct *tty) {
    /* do nothing - not implemented */
}

static void send_break(struct tty_struct *tty) {
    /* do nothing - not implemented */
}

static int get_termios(struct tty_struct *tty, struct termios *termios) {
    int i;

    verify_area(termios, sizeof(*termios));
    for (i = 0; i < (sizeof(*termios)); i++)
        put_fs_byte(((char *)&tty->termios)[i], i + (char *)termios);
    return 0;
}

static int set_termios(struct tty_struct *tty, struct termios *termios) {
    int i;

    for (i = 0; i < (sizeof(*termios)); i++)
        ((char *)&tty->termios)[i] = get_fs_byte(i + (char *)termios);
    change_speed(tty);
    return 0;
}

static int get_termio(struct tty_struct *tty, struct termio *termio) {
    int i;
    struct termio tmp_termio;

    verify_area(termio, sizeof(*termio));
    tmp_termio.c_iflag = tty->termios.c_iflag;
    tmp_termio.c_oflag = tty->termios.c_oflag;
    tmp_termio.c_cflag = tty->termios.c_cflag;
    tmp_termio.c_lflag = tty->termios.c_lflag;
    tmp_termio.c_line = tty->termios.c_line;
    for (i = 0; i < NCC; i++)
        tmp_termio.c_cc[i] = tty->termios.c_cc[i];
    for (i = 0; i < (sizeof(*termio)); i++)
        put_fs_byte(((char *)&tmp_termio)[i], i + (char *)termio);
    return 0;
}

/*
 * This only works as the 386 is low-byt-first
 */
static int set_termio(struct tty_struct *tty, struct termio *termio) {
    int i;
    struct termio tmp_termio;

    for (i = 0; i < (sizeof(*termio)); i++)
        ((char *)&tmp_termio)[i] = get_fs_byte(i + (char *)termio);
    *(unsigned short *)&tty->termios.c_iflag = tmp_termio.c_iflag;
    *(unsigned short *)&tty->termios.c_oflag = tmp_termio.c_oflag;
    *(unsigned short *)&tty->termios.c_cflag = tmp_termio.c_cflag;
    *(unsigned short *)&tty->termios.c_lflag = tmp_termio.c_lflag;
    tty->termios.c_line = tmp_termio.c_line;
    for (i = 0; i < NCC; i++)
        tty->termios.c_cc[i] = tmp_termio.c_cc[i];
    change_speed(tty);
    return 0;
}

int tty_ioctl(int dev, int cmd, int arg) {
    struct tty_struct *tty;
    if (MAJOR(dev) == 5) {
        dev = current->tty;
        if (dev < 0)
            panic("tty_ioctl: dev<0");
    } else
        dev = MINOR(dev);
    tty = dev + tty_table;
    switch (cmd) {
    case TCGETS:
        return get_termios(tty, (struct termios *)arg);
    case TCSETSF:
        flush(&tty->read_q);  /* fallthrough */
    case TCSETSW:
        wait_until_sent(tty); /* fallthrough */
    case TCSETS:
        return set_termios(tty, (struct termios *)arg);
    case TCGETA:
        return get_termio(tty, (struct termio *)arg);
    case TCSETAF:
        flush(&tty->read_q);  /* fallthrough */
    case TCSETAW:
        wait_until_sent(tty); /* fallthrough */
    case TCSETA:
        return set_termio(tty, (struct termio *)arg);
    case TCSBRK:
        if (!arg) {
            wait_until_sent(tty);
            send_break(tty);
        }
        return 0;
    case TCXONC:
        return -EINVAL; /* not implemented */
    case TCFLSH:
        if (arg == 0)
            flush(&tty->read_q);
        else if (arg == 1)
            flush(&tty->write_q);
        else if (arg == 2) {
            flush(&tty->read_q);
            flush(&tty->write_q);
        } else
            return -EINVAL;
        return 0;
    case TIOCEXCL:
        return -EINVAL; /* not implemented */
    case TIOCNXCL:
        return -EINVAL; /* not implemented */
    case TIOCSCTTY:
        return -EINVAL; /* set controlling term NI */
    case TIOCGPGRP:
        verify_area((void *)arg, 4);
        put_fs_long(tty->pgrp, (unsigned long *)arg);
        return 0;
    case TIOCSPGRP:
        tty->pgrp = get_fs_long((unsigned long *)arg);
        return 0;
    case TIOCOUTQ:
        verify_area((void *)arg, 4);
        put_fs_long(CHARS(tty->write_q), (unsigned long *)arg);
        return 0;
    case TIOCINQ:
        verify_area((void *)arg, 4);
        put_fs_long(CHARS(tty->secondary),
                    (unsigned long *)arg);
        return 0;
    case TIOCSTI:
        return -EINVAL; /* not implemented */
    case TIOCGWINSZ:
        return -EINVAL; /* not implemented */
    case TIOCSWINSZ:
        return -EINVAL; /* not implemented */
    case TIOCMGET:
        return -EINVAL; /* not implemented */
    case TIOCMBIS:
        return -EINVAL; /* not implemented */
    case TIOCMBIC:
        return -EINVAL; /* not implemented */
    case TIOCMSET:
        return -EINVAL; /* not implemented */
    case TIOCGSOFTCAR:
        return -EINVAL; /* not implemented */
    case TIOCSSOFTCAR:
        return -EINVAL; /* not implemented */
    default:
        return -EINVAL;
    }
}
